/* Copyright (C) 2008  Romolo Camplani, Marco Fortunato, Marco Marelli, Guido Rota, Fabio A. Schreiber et al.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.dei.perla.sys.device.channel.frame.sender;

import org.dei.perla.sys.device.channel.frame.Frame;
import org.dei.perla.sys.device.channel.frame.FrameConfiguration;
import org.dei.perla.sys.device.channel.frame.header.FrameHeaderSender;


/**
 * Questa classe genera frame da inviare sul canale fisico.
 */
abstract public class FrameSender {

	/**
	 * Parametri di configurazione del Sender
	 */
	protected FrameConfiguration pConfiguration;
	
	/**
	 * Oggetto per creare gli header da spedire per messaggi address full.
	 */
	protected FrameHeaderSender pFrameHeaderSender;
		
	/**
	 * Oggetto cui notificare gli eventi di invio
	 */
	protected FrameSenderEvents pEvents;
	
	/**
	 * Buffer di invio (contiene i byte da inviare sul canale)
	 */
	protected byte []pBuffer;
	
	/**
	 * Numero di byte presenti nel buffer di invio (contando escape e delimitatori)
	 */
	protected int pBufferCount;
	
	/**
	 * Numero di byte inseriti nel buffer di invio (senza contare escape e delimitatori)
	 */
	protected int pByteCount;
	
	/**
	 * Costruttore
	 * @param parConfiguration Parametri di configurazione 
	 */
	public FrameSender(FrameConfiguration parConfiguration) {
		
		// Memorizza l'oggetto con i parametri di configurazione
		this.pConfiguration = parConfiguration;
				
		// Istanzia il buffer locale
		//  NB: Il parametro di configurazione indica la dimensione del buffer di ricezione D.
		//      Il buffer di invio deve essere di (2 * D + 2) per poter contenere gli eventuali 
		//      byte di escape ed i delimitatori di inio e fine frame.
		this.pBuffer = new byte[this.pConfiguration.getBufferSize() * 2 + 2];
		
		// Prepara il Sender per la costruzione del primo frame
		reset();
	}
	
	/**
	 * Inizializza l'oggetto, impostando un gestore eventi di invio
	 * @param parEvents Gestore eventi di invio
	 */
	public void initialize(FrameSenderEvents parEvents) {
		// Memorizza l'oggetto a cui inviare gli eventi
		this.pEvents = parEvents;
	}
	
	/**
	 * Restituisce la configurazione
	 */
	public FrameConfiguration getConfiguration() {
		return this.pConfiguration;
	}
	
	/**
	 * Resetta il buffer di invio
	 */
	public void reset() {
		this.pBufferCount = 0;
		this.pByteCount = 0;
	}
	
	/**
	 * Inizializza il buffer di invio in base al tipo di messaggio da spedire
	 */
	abstract protected void pBufferInitialize(); 
	
	/**
	 * Invia un evento di errore, segnalando la perdita di un byte del frame (buffer pieno)
	 */
	protected void pNotifyBufferOverflow(byte parByte) {
		
		String tErrorMessage = "";
		
		// Costruise il messaggio di errore
		tErrorMessage += "Scartato byte (" + parByte + ") a causa di overflow buffer del buffer di invio"; 
		
		// Invia l'evento
		this.pEvents.onError(tErrorMessage);
	}
		
	/**
	 * Crea un frame da inviare sul canale fisico
	 * @param parAddress Indirizzo sorgente del nodo. Se null invierò pacchetti headerless
	 * @param parPayload Payload
	 * @return Restituisce una stringa di byte
	 */
	abstract public byte []forgeFrame(Frame parFrame); 
	
	/**
	 * Aggiunge un byte al buffer di invio. Restituisce false quando il buffer è pieno.
	 * @param parByteToAppend
	 */
	abstract protected boolean pAppendByte(byte parByteToAppend); 
	
	/**
	 * Restituisce un frame pronto per essere inviato sul canale, quindi resetta
	 * lo stato del FrameSender.
	 * @return 
	 */
	protected byte[] pGetFrame() {
		
		byte []tFrame;
		
		// Aggiunge al buffer il byte delimitatore di fine frame
		this.pBuffer[this.pBufferCount++] = this.pConfiguration.getByteDelimiterEnd();
		
		// Copia il frame in un buffer temporaneo
		tFrame = java.util.Arrays.copyOfRange(this.pBuffer, 0, this.pBufferCount);
		
		// Restituisce il buffer temporaneo
		return tFrame;
	}

}
